package impl

import (
	"errors"
	"fmt"

	"github.com/golang/protobuf/proto"
	bcomm "hundsun.com/hsl/hschain/blockchain/common"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util/pb"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store/mq"
)

// ProcGetBlockMsg 处理区块查询消息
func (chain *BlockChain) ProcGetBlockMsg(req *pbcommon.ReqBlock) (*pbledger.Block, error) {
	return chain.GetBlock(req.Height)
}

// ProcRequestBlockMsg 处理缓存区块查询消息
func (chain *BlockChain) ProcRequestBlockMsg(req *pbcommon.ReqBlocks) ([]*pbledger.Block, error) {
	if req.Start > req.End {
		return nil, types.ErrInvalidParam
	}

	var blks []*pbledger.Block
	chain.log.Debugf("Begin to get block [%d,%d] from block cache", req.Start, req.End)
	for i := req.Start; i <= req.End; i++ {
		entry, ok := chain.blockCache.Get(bcomm.GenTempBlockEntryKeyByHeight(i))
		if !ok {
			break
		}

		blkEntry := entry.(*bcomm.BlockEntry)
		chain.log.Debugf("Got block %d from blockcache.", blkEntry.Block.Header.Number)
		blks = append(blks, blkEntry.Block)
	}
	chain.log.Debugf("Got %d blocks from blockcache", len(blks))
	return blks, nil
}

// ProcGetBlocksMsg 处理区块批量查询消息
func (chain *BlockChain) ProcGetBlocksMsg(req *pbcommon.ReqBlocks) ([]*pbledger.Block, error) {
	blockheight := chain.GetBlockHeight()
	if req.Start > blockheight {
		return nil, types.ErrNotFound
	}
	if req.GetStart() > req.GetEnd() {
		chain.log.Errorf("ProcGetBlocksMsg input invalid, Startheight: %d and Endheight: %d", req.Start, req.End)
		return nil, types.ErrEndLessThanStart
	}

	chain.log.Debugf("ProcGetBlocksMsg, Start: %d and End: %d", req.Start, req.End)

	start := req.Start
	end := req.End
	if req.End > blockheight {
		end = blockheight
	}

	blocks, err := chain.GetBlocks(start, end)
	if err != nil {
		return nil, err
	}

	return blocks, nil
}

// ProcGetBlockExecResultMsg 处理区块执行结果批量查询消息
func (chain *BlockChain) ProcGetBlockExecResultMsg(req *pbcommon.ReqBlock) (*pbledger.BlockExecResult, error) {
	block, err := chain.GetExecutedBlock(req.Height)
	if err != nil {
		return nil, err
	}
	return block.BlockExecResult, nil
}

// ProcGetExecutedBlockMsg 处理执行后区块查询消息
func (chain *BlockChain) ProcGetExecutedBlockMsg(req *pbcommon.ReqBlock) (*pbledger.ExecutedBlock, error) {
	return chain.GetExecutedBlock(req.Height)
}

// ProcGetExecutedBlocksMsg 处理执行后区块批量查询消息
func (chain *BlockChain) ProcGetExecutedBlocksMsg(req *pbcommon.ReqBlocks) ([]*pbledger.ExecutedBlock, error) {
	var blocks []*pbledger.ExecutedBlock

	start := req.Start
	end := req.End
	if start > end {
		return nil, types.ErrInvalidParam
	}

	for height := start; height <= end; height++ {
		block, err := chain.GetExecutedBlock(height)
		if err != nil {
			continue
		}
		blocks = append(blocks, block)
	}
	return blocks, nil
}

// ProcAddBlockMsg 处理缓存区块消息
func (chain *BlockChain) ProcAddBlockMsg(blk *pbledger.Block) error {
	if blk == nil {
		chain.log.Error("ProcAddBlockMsg input block is null")
		return types.ErrInvalidParam
	}

	// 区块合法性校验check
	blockBytes, _ := proto.Marshal(blk)
	blockSize := len(blockBytes)

	maxBlkSize := chain.GetCurParam().GetBlockLimit().MaxBlockSizeBytes
	if uint32(blockSize) > maxBlkSize {
		return fmt.Errorf("blockSize '%d' exceed limit '%d'", len(blockBytes), maxBlkSize)
	}
	chain.log.Infof("save block %d and block size %d", blk.Header.Number, len(blockBytes))

	err := pb.ValidateBasicInfoOfBlock(blk, chain.GetCurParam().GetFeeLimit().GetMinTxFee(), chain.GetCurParam().GetReward())
	if err != nil {
		chain.log.Debugf("block basic info validate failed, %s", err.Error())
		return err
	}

	if !blk.Verify() {
		chain.log.Debugf("block signature validate failed, %s", err.Error())
		return types.ErrInvalidSignature
	}

	chain.log.Debug("block signature validated")

	var blkEntry = &bcomm.BlockEntry{}
	blkEntry.Block = blk
	blkEntry.Checked = false

	tempBlkEntryKey := bcomm.GenTempBlockEntryKeyByHeight(blk.Header.Number)
	err = chain.blockCache.Add(tempBlkEntryKey, blkEntry)
	if err != nil {
		chain.log.Debugf("Process add block, %s", err.Error())
	}

	return nil
}

// ProcAddBroadcastBlockMsg 处理缓存接收到的广播区块
func (chain *BlockChain) ProcAddBroadcastBlockMsg(blks []*pbledger.Block) error {
	for _, block := range blks {
		if block != nil {
			blkHeight := block.Header.Number
			var entry = &bcomm.BlockEntry{block, false}
			tempBlkEntryKey := bcomm.GenTempBlockEntryKeyByHeight(blkHeight)
			err := chain.blockCache.Add(tempBlkEntryKey, entry)
			if err != nil {
				chain.log.Debugf("block %d already existed in blockcache.", blkHeight)
			}
			if block.Header.Number == chain.GetBlockHeight()+1 {
				chain.UpdateReceivedBlock(block)
			}
			continue
		}
		chain.log.Debugf("got nil block")
	}

	return nil
}

// ProcSaveBlockMsg 处理区块存储消息
func (chain *BlockChain) ProcSaveBlockMsg(blkNum uint64) error {
	tempBlkKey := bcomm.GenTempBlockEntryKeyByHeight(blkNum)
	entry, ok := chain.blockCache.Get(tempBlkKey)
	if !ok {
		return types.ErrNotFound
	}

	var blockEntry = entry.(*bcomm.BlockEntry)
	if blockEntry.Checked {
		return errors.New("block already checked")
	}
	blockEntry.Checked = true
	chain.blockCache.Set(tempBlkKey, blockEntry)
	chain.log.Debugf("Block %d will save to blockchain soon.", blkNum)
	return nil
}

// ProcSaveBlockExecRstMsg 处理保存区块执行结果消息
func (chain *BlockChain) ProcSaveBlockExecRstMsg(result *pbledger.BlockExecResult) error {
	return chain.blockStore.SaveBlockExecResult(result)
}

// ProcGetLastBlockMsg 处理获取最新执行后区块消息
func (chain *BlockChain) ProcGetLastBlockMsg() (*pbledger.ExecutedBlock, error) {
	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		return nil, err
	}

	maxExecNum, err := service.GetMaxExecutedBlockNumber()
	if err != nil {
		return nil, err
	}

	var blk *pbledger.Block
	var blkExecRes *pbledger.BlockExecResult
	var eb = &pbledger.ExecutedBlock{}

	if maxExecNum == 0 {
		if entry, ok := chain.blockCache.Get(bcomm.GenTempBlockEntryKeyByHeight(0)); ok {
			blk = entry.(*bcomm.BlockEntry).Block
			blkExecRes, err = service.GetBlockExecResult(maxExecNum)
			if err != nil {
				return nil, err
			}
		} else {
			return nil, types.ErrNotFound
		}
	} else {
		blk, err = service.GetBlock(maxExecNum)
		if err != nil {
			return nil, err
		}

		blkExecRes, err = service.GetBlockExecResult(maxExecNum)
		if err != nil {
			return nil, err
		}
	}

	eb.Block = blk
	eb.BlockExecResult = blkExecRes

	return eb, nil
}

// GetBlock 根据高度查询指定区块
func (chain *BlockChain) GetBlock(height uint64) (*pbledger.Block, error) {
	//从缓存的最新区块中尝试获取，最新区块的add是在执行block流程中处理
	if blkEntry, ok := chain.blockCache.Get(bcomm.GenTempBlockEntryKeyByHeight(height)); ok {
		blk := blkEntry.(*bcomm.BlockEntry).Block
		chain.log.Debug("Get executed block from block cache.")
		return blk, nil
	}

	//从blockstore缓存的活跃区块中尝试获取
	executedBlk, _ := chain.blockStore.GetActiveBlock(height)
	if executedBlk != nil {
		chain.log.Infof("GetBlock GetExecutedBlock from blockstore active blocks", "height", height)
		return executedBlk.Block, nil
	}
	blockinfo, err := chain.blockStore.GetBlock(height)
	if err != nil {
		return nil, err
	}

	if len(blockinfo.Transactions) != 0 {
		chain.log.Debug("GetBlock  receive empty block", "height", height)
	}
	return blockinfo, nil
}

// GetBlocks 根据高度查询指定区块
func (chain *BlockChain) GetBlocks(start, end uint64) ([]*pbledger.Block, error) {
	blocks, err := chain.blockStore.GetBlocks(start, end)
	if err != nil {
		return nil, err
	}

	return blocks, nil
}

// GetExecutedBlock 用于获取指定高度的block，首先在缓存中获取，如果不存在就从db中获取
func (chain *BlockChain) GetExecutedBlock(height uint64) (block *pbledger.ExecutedBlock, err error) {
	executedBlk, err := chain.GetCacheExecutedBlock(height)
	if executedBlk != nil {
		if len(executedBlk.BlockExecResult.TransactionExecResults) == 0 && len(executedBlk.Block.Transactions) != 0 {
			chain.log.Debug("GetBlock  CheckcacheBlock Receipts ==0", "height", height)
		}
		chain.log.Debug("Get executed block from block cache.")
		return executedBlk, nil
	}

	//从blockstore缓存的活跃区块中尝试获取
	executedBlk, err = chain.blockStore.GetActiveBlock(height)
	if executedBlk != nil {
		chain.log.Error("GetBlock GetExecutedBlock from blockstore active blocks", "height", height, "error", err)
		return executedBlk, nil
	}
	blockinfo, err := chain.blockStore.GetExecutedBlock(height)
	if err != nil {
		chain.log.Tracef("Get executed block %d from blockstore failed, err:%s", height, err.Error())
		return nil, err
	}

	if len(blockinfo.BlockExecResult.TransactionExecResults) == 0 && len(blockinfo.Block.Transactions) != 0 {
		chain.log.Debug("GetBlock LoadBlock Receipts ==0", "height", height)
	}
	return blockinfo, nil
}

// GetBlockHeight 获取blockchain当前区块高度
func (chain *BlockChain) GetBlockHeight() uint64 {
	return chain.blockStore.height.Load()
}

// ProcessGetMaxBlockNumber 获取最大区块高度
func (chain *BlockChain) ProcessGetMaxBlockNumber() (uint64, error) {
	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		return 0, nil
	}

	maxBlkNum, err := service.GetBlockNumber()
	if err != nil {
		return 0, nil
	}
	return maxBlkNum, nil
}

// ProcessGetMaxBlockSequence 获取最大block sequence
func (chain *BlockChain) ProcessGetMaxBlockSequence() (uint64, error) {
	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		return 0, nil
	}

	maxBlkSeq, err := service.GetBlockSequence()
	if err != nil {
		return 0, nil
	}
	return maxBlkSeq, nil
}

// ProcessGetMaxExecutedBlockNumber 获取最大执行后区块高度
func (chain *BlockChain) ProcessGetMaxExecutedBlockNumber() (uint64, error) {
	service, err := chain.blockStore.GetLedgerService()
	if err != nil {
		return 0, nil
	}

	maxExectuedBlkNum, err := service.GetMaxExecutedBlockNumber()
	if err != nil {
		return 0, nil
	}
	return maxExectuedBlkNum, nil
}

// UpdateReceivedBlock 更新最新接受区块信息
func (chain *BlockChain) UpdateReceivedBlock(block *pbledger.Block) error {
	receivedBlkMsg := chain.topicClient.NewMessage(types.ConsensusModule, mq.ModeTopic.String(), types.EventReceivedBlock, block)
	err := chain.topicClient.Send(receivedBlkMsg, false)
	if err != nil {
		chain.log.Error("Send received block msg failed, err: %s\n", err.Error())
		return err
	}
	return nil
}

// DeletePendingBlock 删除缓存中指定高度的区块
func (chain *BlockChain) DeletePendingBlock(height uint64) {
	tempBlkEntryKey := bcomm.GenTempBlockEntryKeyByHeight(height)
	if !chain.blockCache.Contains(tempBlkEntryKey) {
		chain.log.Errorf("Pending block %d not existed in blockcache", height)
	} else {
		chain.blockCache.Delete(tempBlkEntryKey)
	}
}
